Load packages

library("Thermimage")
library("fields")
library("imager")
library("magrittr")
library("spatstat")

Set configurations

## Noise filering
NF = TRUE
## Histgoram Equalization
HE = TRUE

## INPUT_PATH_FOLDER
input = "../Data/"
## OUTPUT_PATH_FOLDER
output = "4_HE_NF/"

## Gassian sigma value for noise filtering
sigma = 0.8

## Situational parameters
OD = 10
RH = 53
AtmosT = 28

Load path images from directory

filenames <- list.files(input, pattern="*.JPG", full.names=FALSE)
filenames
  [1] "DJI_0001_R.JPG" "DJI_0003_R.JPG" "DJI_0005_R.JPG" "DJI_0007_R.JPG" "DJI_0009_R.JPG" "DJI_0011_R.JPG"
  [7] "DJI_0013_R.JPG" "DJI_0015_R.JPG" "DJI_0017_R.JPG" "DJI_0019_R.JPG" "DJI_0021_R.JPG" "DJI_0023_R.JPG"
 [13] "DJI_0025_R.JPG" "DJI_0027_R.JPG" "DJI_0029_R.JPG" "DJI_0031_R.JPG" "DJI_0033_R.JPG" "DJI_0035_R.JPG"
 [19] "DJI_0037_R.JPG" "DJI_0039_R.JPG" "DJI_0041_R.JPG" "DJI_0043_R.JPG" "DJI_0045_R.JPG" "DJI_0047_R.JPG"
 [25] "DJI_0049_R.JPG" "DJI_0051_R.JPG" "DJI_0053_R.JPG" "DJI_0055_R.JPG" "DJI_0057_R.JPG" "DJI_0059_R.JPG"
 [31] "DJI_0061_R.JPG" "DJI_0063_R.JPG" "DJI_0065_R.JPG" "DJI_0067_R.JPG" "DJI_0069_R.JPG" "DJI_0071_R.JPG"
 [37] "DJI_0073_R.JPG" "DJI_0075_R.JPG" "DJI_0077_R.JPG" "DJI_0079_R.JPG" "DJI_0081_R.JPG" "DJI_0083_R.JPG"
 [43] "DJI_0085_R.JPG" "DJI_0087_R.JPG" "DJI_0089_R.JPG" "DJI_0091_R.JPG" "DJI_0093_R.JPG" "DJI_0095_R.JPG"
 [49] "DJI_0097_R.JPG" "DJI_0099_R.JPG" "DJI_0101_R.JPG" "DJI_0103_R.JPG" "DJI_0105_R.JPG" "DJI_0107_R.JPG"
 [55] "DJI_0109_R.JPG" "DJI_0111_R.JPG" "DJI_0113_R.JPG" "DJI_0115_R.JPG" "DJI_0117_R.JPG" "DJI_0119_R.JPG"
 [61] "DJI_0121_R.JPG" "DJI_0123_R.JPG" "DJI_0125_R.JPG" "DJI_0127_R.JPG" "DJI_0129_R.JPG" "DJI_0131_R.JPG"
 [67] "DJI_0133_R.JPG" "DJI_0135_R.JPG" "DJI_0137_R.JPG" "DJI_0139_R.JPG" "DJI_0141_R.JPG" "DJI_0143_R.JPG"
 [73] "DJI_0145_R.JPG" "DJI_0147_R.JPG" "DJI_0149_R.JPG" "DJI_0151_R.JPG" "DJI_0153_R.JPG" "DJI_0155_R.JPG"
 [79] "DJI_0157_R.JPG" "DJI_0159_R.JPG" "DJI_0161_R.JPG" "DJI_0163_R.JPG" "DJI_0165_R.JPG" "DJI_0167_R.JPG"
 [85] "DJI_0169_R.JPG" "DJI_0171_R.JPG" "DJI_0173_R.JPG" "DJI_0175_R.JPG" "DJI_0177_R.JPG" "DJI_0179_R.JPG"
 [91] "DJI_0181_R.JPG" "DJI_0183_R.JPG" "DJI_0185_R.JPG" "DJI_0187_R.JPG" "DJI_0189_R.JPG" "DJI_0191_R.JPG"
 [97] "DJI_0193_R.JPG" "DJI_0195_R.JPG" "DJI_0197_R.JPG" "DJI_0199_R.JPG" "DJI_0201_R.JPG" "DJI_0203_R.JPG"
[103] "DJI_0205_R.JPG" "DJI_0207_R.JPG" "DJI_0209_R.JPG" "DJI_0211_R.JPG" "DJI_0213_R.JPG" "DJI_0215_R.JPG"
[109] "DJI_0217_R.JPG" "DJI_0219_R.JPG" "DJI_0221_R.JPG" "DJI_0223_R.JPG" "DJI_0225_R.JPG" "DJI_0227_R.JPG"
[115] "DJI_0229_R.JPG" "DJI_0231_R.JPG" "DJI_0233_R.JPG" "DJI_0235_R.JPG" "DJI_0237_R.JPG" "DJI_0239_R.JPG"
[121] "DJI_0241_R.JPG" "DJI_0243_R.JPG" "DJI_0245_R.JPG" "DJI_0247_R.JPG" "DJI_0249_R.JPG" "DJI_0251_R.JPG"
[127] "DJI_0253_R.JPG" "DJI_0255_R.JPG" "DJI_0257_R.JPG" "DJI_0259_R.JPG" "DJI_0261_R.JPG" "DJI_0263_R.JPG"
[133] "DJI_0265_R.JPG" "DJI_0267_R.JPG" "DJI_0269_R.JPG" "DJI_0271_R.JPG" "DJI_0273_R.JPG" "DJI_0275_R.JPG"
[139] "DJI_0277_R.JPG" "DJI_0279_R.JPG" "DJI_0281_R.JPG" "DJI_0283_R.JPG" "DJI_0285_R.JPG" "DJI_0287_R.JPG"
[145] "DJI_0289_R.JPG" "DJI_0291_R.JPG" "DJI_0293_R.JPG" "DJI_0295_R.JPG" "DJI_0297_R.JPG" "DJI_0299_R.JPG"
[151] "DJI_0301_R.JPG" "DJI_0303_R.JPG" "DJI_0305_R.JPG" "DJI_0307_R.JPG" "DJI_0309_R.JPG" "DJI_0311_R.JPG"
[157] "DJI_0313_R.JPG"

Read image function

# Create a function with arguments.
new.read_image <- function(image_file, 
                           input_path="", output_path="Output/",
                           NF=FALSE, 
                           sigma=0.8,
                           HE=FALSE,
                           OD=NULL,
                           RH=NULL,
                           AtmosT=NULL) {
  ## Create image path
  image_path = paste(input_path, image_file, sep="")

  ## Extract meta-tags from thermal image file ##
  cams<-flirsettings(image_path, exiftool="installed", camvals="")

  ## Set variables for calculation of temperature values from raw A/D sensor data
  Emissivity<-cams$Info$Emissivity      # Image Saved Emissivity - should be ~0.95 or 0.96
  ObjectEmissivity<-0.96                # Object Emissivity - should be ~0.95 or 0.96
  dateOriginal<-cams$Dates$DateTimeOriginal
  dateModif<-   cams$Dates$FileModificationDateTime
  PlanckR1<-    cams$Info$PlanckR1                      # Planck R1 constant for camera
  PlanckB<-     cams$Info$PlanckB                       # Planck B constant for camera
  PlanckF<-     cams$Info$PlanckF                       # Planck F constant for camera
  PlanckO<-     cams$Info$PlanckO                       # Planck O constant for camera
  PlanckR2<-    cams$Info$PlanckR2                      # Planck R2 constant for camera
  ATA1<-        cams$Info$AtmosphericTransAlpha1        # Atmospheric attenuation constant
  ATA2<-        cams$Info$AtmosphericTransAlpha2        # Atmospheric attenuation constant
  ATB1<-        cams$Info$AtmosphericTransBeta1         # Atmospheric attenuation constant
  ATB2<-        cams$Info$AtmosphericTransBeta2         # Atmospheric attenuation constant
  ATX<-         cams$Info$AtmosphericTransX             # Atmospheric attenuation constant
  if (is.null(OD)) {
    OD<-          cams$Info$ObjectDistance                # object distance in metres
  }
  FD<-          cams$Info$FocusDistance                 # focus distance in metres
  ReflT<-       cams$Info$ReflectedApparentTemperature  # Reflected apparent temperature
  if (is.null(AtmosT)) {
    AtmosT<-      cams$Info$AtmosphericTemperature        # Atmospheric temperature
  }
  IRWinT<-      cams$Info$IRWindowTemperature           # IR Window Temperature
  IRWinTran<-   cams$Info$IRWindowTransmission          # IR Window transparency
  if (is.null(RH)) {
    RH<-          cams$Info$RelativeHumidity              # Relative Humidity
  }
  h<-           cams$Info$RawThermalImageHeight         # sensor height (i.e. image height)
  w<-           cams$Info$RawThermalImageWidth          # sensor width (i.e. image width)

  ## Rotate image
  temp_image = rotate270.matrix(readflirJPG(image_path))

  ## Convert raw to temp data
  temp_image = raw2temp(temp_image, ObjectEmissivity, OD, ReflT, AtmosT, IRWinT, IRWinTran, RH,
                        PlanckR1, PlanckB, PlanckF, PlanckO, PlanckR2)
  
  ## Noise filtering with gauss filter
  if (NF == TRUE) {
    temp_image =  as.im(temp_image)
    temp_image = blur(temp_image, sigma, bleed=FALSE)
    temp_image =  as.matrix(temp_image)
  }

  
  # hist(temp_image, main=paste("Histogram ", image_file))
  ## Save temperature image
  saveRDS(temp_image, file = paste(output_path, tools::file_path_sans_ext(image_file), ".RData", sep=""))

  ## Return either min or max or every temperature value
  if (HE == TRUE) {
    temp_image
  } else {
    min <- min(temp_image)
    max <- max(temp_image)
    values <- c(min, max)
  }
}

Load images

glob_temp_range <- unlist(lapply(filenames, new.read_image, 
                                 input_path=input,
                                 output_path=output,
                                 NF=NF,
                                 sigma=sigma,
                                 HE=HE,
                                 OD=OD,
                                 RH=RH,
                                 AtmosT=AtmosT))

Calculate global min and max values

min_temp <- min(glob_temp_range)
max_temp <- max(glob_temp_range)

Calculate ECDF

if (HE == TRUE) {
  f <- ecdf(glob_temp_range)
  hist(glob_temp_range, main=paste("Global Histogram"))
  hist(f(glob_temp_range), main=paste("Global Equalized Histogram"))
} else {
  f <- NULL
}

Normalize images

new.normalize <- function(image_file, output_path="4_HE_NF/",
                          min, max, 
                          HE=FALSE, f_ecdf=NULL) {
  ## Create image path
  image_path = paste(output_path, tools::file_path_sans_ext(image_file), ".RData", sep="")
  temp_image <- readRDS(image_path)
  # unlink(image_path)
  
  ## Normalize data
  temp_image <- mirror.matrix(temp_image)

  if (HE == TRUE) {
    equalized <- f_ecdf(temp_image)
    equalized <- as.cimg(equalized, dim=dim(temp_image)) 
    plot(equalized, rescale=FALSE, main=paste("Histogram equalized", image_file))
    save.image(equalized, file=paste(output_path, image_file, sep=""), quality = 1)
  } else {
    temp_image <- (temp_image - min) / (max - min)
    # hist(x=temp_image, main=paste("Histogram of normalized values", image_path), xlab='normalized value')

    ## Plot and save temperature image
    temp_image = as.cimg(temp_image, dim=dim(temp_image))
    save.image(temp_image, file=paste(output_path, image_file, sep=""), quality = 1)
    plot(temp_image, rescale=FALSE)
  }
}

Plot normalized images

temp = lapply(filenames, new.normalize, 
              output_path=output, 
              min=min_temp, max=max_temp, 
              HE=HE, 
              f_ecdf=f)

LS0tCnRpdGxlOiAiQmF0Y2ggQ2FsaWJyYXRpb24gVGhlcm1vZ3JhbSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKTG9hZCBwYWNrYWdlcwpgYGB7cn0KbGlicmFyeSgiVGhlcm1pbWFnZSIpCmxpYnJhcnkoImZpZWxkcyIpCmxpYnJhcnkoImltYWdlciIpCmxpYnJhcnkoIm1hZ3JpdHRyIikKbGlicmFyeSgic3BhdHN0YXQiKQpgYGAKClNldCBjb25maWd1cmF0aW9ucwoKYGBge3J9CiMjIE5vaXNlIGZpbGVyaW5nCk5GID0gVFJVRQojIyBIaXN0Z29yYW0gRXF1YWxpemF0aW9uCkhFID0gVFJVRQoKIyMgSU5QVVRfUEFUSF9GT0xERVIKaW5wdXQgPSAiLi4vRGF0YS8iCiMjIE9VVFBVVF9QQVRIX0ZPTERFUgpvdXRwdXQgPSAiNF9IRV9ORi8iCgojIyBHYXNzaWFuIHNpZ21hIHZhbHVlIGZvciBub2lzZSBmaWx0ZXJpbmcKc2lnbWEgPSAwLjgKCiMjIFNpdHVhdGlvbmFsIHBhcmFtZXRlcnMKT0QgPSAxMApSSCA9IDUzCkF0bW9zVCA9IDI4CmBgYAoKTG9hZCBwYXRoIGltYWdlcyBmcm9tIGRpcmVjdG9yeQoKYGBge3J9CmZpbGVuYW1lcyA8LSBsaXN0LmZpbGVzKGlucHV0LCBwYXR0ZXJuPSIqLkpQRyIsIGZ1bGwubmFtZXM9RkFMU0UpCmZpbGVuYW1lcwpgYGAKClJlYWQgaW1hZ2UgZnVuY3Rpb24KCmBgYHtyfQojIENyZWF0ZSBhIGZ1bmN0aW9uIHdpdGggYXJndW1lbnRzLgpuZXcucmVhZF9pbWFnZSA8LSBmdW5jdGlvbihpbWFnZV9maWxlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRfcGF0aD0iIiwgb3V0cHV0X3BhdGg9Ik91dHB1dC8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICBORj1GQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpZ21hPTAuOCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgSEU9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIE9EPU5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFJIPU5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEF0bW9zVD1OVUxMKSB7CiAgIyMgQ3JlYXRlIGltYWdlIHBhdGgKICBpbWFnZV9wYXRoID0gcGFzdGUoaW5wdXRfcGF0aCwgaW1hZ2VfZmlsZSwgc2VwPSIiKQoKICAjIyBFeHRyYWN0IG1ldGEtdGFncyBmcm9tIHRoZXJtYWwgaW1hZ2UgZmlsZSAjIwogIGNhbXM8LWZsaXJzZXR0aW5ncyhpbWFnZV9wYXRoLCBleGlmdG9vbD0iaW5zdGFsbGVkIiwgY2FtdmFscz0iIikKCiAgIyMgU2V0IHZhcmlhYmxlcyBmb3IgY2FsY3VsYXRpb24gb2YgdGVtcGVyYXR1cmUgdmFsdWVzIGZyb20gcmF3IEEvRCBzZW5zb3IgZGF0YQogIEVtaXNzaXZpdHk8LWNhbXMkSW5mbyRFbWlzc2l2aXR5ICAgICAgIyBJbWFnZSBTYXZlZCBFbWlzc2l2aXR5IC0gc2hvdWxkIGJlIH4wLjk1IG9yIDAuOTYKICBPYmplY3RFbWlzc2l2aXR5PC0wLjk2ICAgICAgICAgICAgICAgICMgT2JqZWN0IEVtaXNzaXZpdHkgLSBzaG91bGQgYmUgfjAuOTUgb3IgMC45NgogIGRhdGVPcmlnaW5hbDwtY2FtcyREYXRlcyREYXRlVGltZU9yaWdpbmFsCiAgZGF0ZU1vZGlmPC0gICBjYW1zJERhdGVzJEZpbGVNb2RpZmljYXRpb25EYXRlVGltZQogIFBsYW5ja1IxPC0gICAgY2FtcyRJbmZvJFBsYW5ja1IxICAgICAgICAgICAgICAgICAgICAgICMgUGxhbmNrIFIxIGNvbnN0YW50IGZvciBjYW1lcmEKICBQbGFuY2tCPC0gICAgIGNhbXMkSW5mbyRQbGFuY2tCICAgICAgICAgICAgICAgICAgICAgICAjIFBsYW5jayBCIGNvbnN0YW50IGZvciBjYW1lcmEKICBQbGFuY2tGPC0gICAgIGNhbXMkSW5mbyRQbGFuY2tGICAgICAgICAgICAgICAgICAgICAgICAjIFBsYW5jayBGIGNvbnN0YW50IGZvciBjYW1lcmEKICBQbGFuY2tPPC0gICAgIGNhbXMkSW5mbyRQbGFuY2tPICAgICAgICAgICAgICAgICAgICAgICAjIFBsYW5jayBPIGNvbnN0YW50IGZvciBjYW1lcmEKICBQbGFuY2tSMjwtICAgIGNhbXMkSW5mbyRQbGFuY2tSMiAgICAgICAgICAgICAgICAgICAgICAjIFBsYW5jayBSMiBjb25zdGFudCBmb3IgY2FtZXJhCiAgQVRBMTwtICAgICAgICBjYW1zJEluZm8kQXRtb3NwaGVyaWNUcmFuc0FscGhhMSAgICAgICAgIyBBdG1vc3BoZXJpYyBhdHRlbnVhdGlvbiBjb25zdGFudAogIEFUQTI8LSAgICAgICAgY2FtcyRJbmZvJEF0bW9zcGhlcmljVHJhbnNBbHBoYTIgICAgICAgICMgQXRtb3NwaGVyaWMgYXR0ZW51YXRpb24gY29uc3RhbnQKICBBVEIxPC0gICAgICAgIGNhbXMkSW5mbyRBdG1vc3BoZXJpY1RyYW5zQmV0YTEgICAgICAgICAjIEF0bW9zcGhlcmljIGF0dGVudWF0aW9uIGNvbnN0YW50CiAgQVRCMjwtICAgICAgICBjYW1zJEluZm8kQXRtb3NwaGVyaWNUcmFuc0JldGEyICAgICAgICAgIyBBdG1vc3BoZXJpYyBhdHRlbnVhdGlvbiBjb25zdGFudAogIEFUWDwtICAgICAgICAgY2FtcyRJbmZvJEF0bW9zcGhlcmljVHJhbnNYICAgICAgICAgICAgICMgQXRtb3NwaGVyaWMgYXR0ZW51YXRpb24gY29uc3RhbnQKICBpZiAoaXMubnVsbChPRCkpIHsKICAgIE9EPC0gICAgICAgICAgY2FtcyRJbmZvJE9iamVjdERpc3RhbmNlICAgICAgICAgICAgICAgICMgb2JqZWN0IGRpc3RhbmNlIGluIG1ldHJlcwogIH0KICBGRDwtICAgICAgICAgIGNhbXMkSW5mbyRGb2N1c0Rpc3RhbmNlICAgICAgICAgICAgICAgICAjIGZvY3VzIGRpc3RhbmNlIGluIG1ldHJlcwogIFJlZmxUPC0gICAgICAgY2FtcyRJbmZvJFJlZmxlY3RlZEFwcGFyZW50VGVtcGVyYXR1cmUgICMgUmVmbGVjdGVkIGFwcGFyZW50IHRlbXBlcmF0dXJlCiAgaWYgKGlzLm51bGwoQXRtb3NUKSkgewogICAgQXRtb3NUPC0gICAgICBjYW1zJEluZm8kQXRtb3NwaGVyaWNUZW1wZXJhdHVyZSAgICAgICAgIyBBdG1vc3BoZXJpYyB0ZW1wZXJhdHVyZQogIH0KICBJUldpblQ8LSAgICAgIGNhbXMkSW5mbyRJUldpbmRvd1RlbXBlcmF0dXJlICAgICAgICAgICAjIElSIFdpbmRvdyBUZW1wZXJhdHVyZQogIElSV2luVHJhbjwtICAgY2FtcyRJbmZvJElSV2luZG93VHJhbnNtaXNzaW9uICAgICAgICAgICMgSVIgV2luZG93IHRyYW5zcGFyZW5jeQogIGlmIChpcy5udWxsKFJIKSkgewogICAgUkg8LSAgICAgICAgICBjYW1zJEluZm8kUmVsYXRpdmVIdW1pZGl0eSAgICAgICAgICAgICAgIyBSZWxhdGl2ZSBIdW1pZGl0eQogIH0KICBoPC0gICAgICAgICAgIGNhbXMkSW5mbyRSYXdUaGVybWFsSW1hZ2VIZWlnaHQgICAgICAgICAjIHNlbnNvciBoZWlnaHQgKGkuZS4gaW1hZ2UgaGVpZ2h0KQogIHc8LSAgICAgICAgICAgY2FtcyRJbmZvJFJhd1RoZXJtYWxJbWFnZVdpZHRoICAgICAgICAgICMgc2Vuc29yIHdpZHRoIChpLmUuIGltYWdlIHdpZHRoKQoKICAjIyBSb3RhdGUgaW1hZ2UKICB0ZW1wX2ltYWdlID0gcm90YXRlMjcwLm1hdHJpeChyZWFkZmxpckpQRyhpbWFnZV9wYXRoKSkKCiAgIyMgQ29udmVydCByYXcgdG8gdGVtcCBkYXRhCiAgdGVtcF9pbWFnZSA9IHJhdzJ0ZW1wKHRlbXBfaW1hZ2UsIE9iamVjdEVtaXNzaXZpdHksIE9ELCBSZWZsVCwgQXRtb3NULCBJUldpblQsIElSV2luVHJhbiwgUkgsCiAgICAgICAgICAgICAgICAgICAgICAgIFBsYW5ja1IxLCBQbGFuY2tCLCBQbGFuY2tGLCBQbGFuY2tPLCBQbGFuY2tSMikKICAKICAjIyBOb2lzZSBmaWx0ZXJpbmcgd2l0aCBnYXVzcyBmaWx0ZXIKICBpZiAoTkYgPT0gVFJVRSkgewogICAgdGVtcF9pbWFnZSA9ICBhcy5pbSh0ZW1wX2ltYWdlKQogICAgdGVtcF9pbWFnZSA9IGJsdXIodGVtcF9pbWFnZSwgc2lnbWEsIGJsZWVkPUZBTFNFKQogICAgdGVtcF9pbWFnZSA9ICBhcy5tYXRyaXgodGVtcF9pbWFnZSkKICB9CgogIAogICMgaGlzdCh0ZW1wX2ltYWdlLCBtYWluPXBhc3RlKCJIaXN0b2dyYW0gIiwgaW1hZ2VfZmlsZSkpCiAgIyMgU2F2ZSB0ZW1wZXJhdHVyZSBpbWFnZQogIHNhdmVSRFModGVtcF9pbWFnZSwgZmlsZSA9IHBhc3RlKG91dHB1dF9wYXRoLCB0b29sczo6ZmlsZV9wYXRoX3NhbnNfZXh0KGltYWdlX2ZpbGUpLCAiLlJEYXRhIiwgc2VwPSIiKSkKCiAgIyMgUmV0dXJuIGVpdGhlciBtaW4gb3IgbWF4IG9yIGV2ZXJ5IHRlbXBlcmF0dXJlIHZhbHVlCiAgaWYgKEhFID09IFRSVUUpIHsKICAgIHRlbXBfaW1hZ2UKICB9IGVsc2UgewogICAgbWluIDwtIG1pbih0ZW1wX2ltYWdlKQogICAgbWF4IDwtIG1heCh0ZW1wX2ltYWdlKQogICAgdmFsdWVzIDwtIGMobWluLCBtYXgpCiAgfQp9CmBgYAoKTG9hZCBpbWFnZXMKCmBgYHtyfQpnbG9iX3RlbXBfcmFuZ2UgPC0gdW5saXN0KGxhcHBseShmaWxlbmFtZXMsIG5ldy5yZWFkX2ltYWdlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRfcGF0aD1pbnB1dCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0X3BhdGg9b3V0cHV0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORj1ORiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lnbWE9c2lnbWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEhFPUhFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPRD1PRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUkg9UkgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEF0bW9zVD1BdG1vc1QpKQpgYGAKCkNhbGN1bGF0ZSBnbG9iYWwgbWluIGFuZCBtYXggdmFsdWVzCgpgYGB7cn0KbWluX3RlbXAgPC0gbWluKGdsb2JfdGVtcF9yYW5nZSkKbWF4X3RlbXAgPC0gbWF4KGdsb2JfdGVtcF9yYW5nZSkKYGBgCgpDYWxjdWxhdGUgRUNERgoKYGBge3J9CmlmIChIRSA9PSBUUlVFKSB7CiAgZiA8LSBlY2RmKGdsb2JfdGVtcF9yYW5nZSkKICBoaXN0KGdsb2JfdGVtcF9yYW5nZSwgbWFpbj1wYXN0ZSgiR2xvYmFsIEhpc3RvZ3JhbSIpKQogIGhpc3QoZihnbG9iX3RlbXBfcmFuZ2UpLCBtYWluPXBhc3RlKCJHbG9iYWwgRXF1YWxpemVkIEhpc3RvZ3JhbSIpKQp9IGVsc2UgewogIGYgPC0gTlVMTAp9CmBgYAoKTm9ybWFsaXplIGltYWdlcwoKYGBge3J9Cm5ldy5ub3JtYWxpemUgPC0gZnVuY3Rpb24oaW1hZ2VfZmlsZSwgb3V0cHV0X3BhdGg9IjRfSEVfTkYvIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4sIG1heCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgSEU9RkFMU0UsIGZfZWNkZj1OVUxMKSB7CiAgIyMgQ3JlYXRlIGltYWdlIHBhdGgKICBpbWFnZV9wYXRoID0gcGFzdGUob3V0cHV0X3BhdGgsIHRvb2xzOjpmaWxlX3BhdGhfc2Fuc19leHQoaW1hZ2VfZmlsZSksICIuUkRhdGEiLCBzZXA9IiIpCiAgdGVtcF9pbWFnZSA8LSByZWFkUkRTKGltYWdlX3BhdGgpCiAgIyB1bmxpbmsoaW1hZ2VfcGF0aCkKICAKICAjIyBOb3JtYWxpemUgZGF0YQogIHRlbXBfaW1hZ2UgPC0gbWlycm9yLm1hdHJpeCh0ZW1wX2ltYWdlKQoKICBpZiAoSEUgPT0gVFJVRSkgewogICAgZXF1YWxpemVkIDwtIGZfZWNkZih0ZW1wX2ltYWdlKQogICAgZXF1YWxpemVkIDwtIGFzLmNpbWcoZXF1YWxpemVkLCBkaW09ZGltKHRlbXBfaW1hZ2UpKSAKICAgIHBsb3QoZXF1YWxpemVkLCByZXNjYWxlPUZBTFNFLCBtYWluPXBhc3RlKCJIaXN0b2dyYW0gZXF1YWxpemVkIiwgaW1hZ2VfZmlsZSkpCiAgICBzYXZlLmltYWdlKGVxdWFsaXplZCwgZmlsZT1wYXN0ZShvdXRwdXRfcGF0aCwgaW1hZ2VfZmlsZSwgc2VwPSIiKSwgcXVhbGl0eSA9IDEpCiAgfSBlbHNlIHsKICAgIHRlbXBfaW1hZ2UgPC0gKHRlbXBfaW1hZ2UgLSBtaW4pIC8gKG1heCAtIG1pbikKICAgICMgaGlzdCh4PXRlbXBfaW1hZ2UsIG1haW49cGFzdGUoIkhpc3RvZ3JhbSBvZiBub3JtYWxpemVkIHZhbHVlcyIsIGltYWdlX3BhdGgpLCB4bGFiPSdub3JtYWxpemVkIHZhbHVlJykKCiAgICAjIyBQbG90IGFuZCBzYXZlIHRlbXBlcmF0dXJlIGltYWdlCiAgICB0ZW1wX2ltYWdlID0gYXMuY2ltZyh0ZW1wX2ltYWdlLCBkaW09ZGltKHRlbXBfaW1hZ2UpKQogICAgc2F2ZS5pbWFnZSh0ZW1wX2ltYWdlLCBmaWxlPXBhc3RlKG91dHB1dF9wYXRoLCBpbWFnZV9maWxlLCBzZXA9IiIpLCBxdWFsaXR5ID0gMSkKICAgIHBsb3QodGVtcF9pbWFnZSwgcmVzY2FsZT1GQUxTRSkKICB9Cn0KYGBgCgpQbG90IG5vcm1hbGl6ZWQgaW1hZ2VzCgpgYGB7cn0KdGVtcCA9IGxhcHBseShmaWxlbmFtZXMsIG5ldy5ub3JtYWxpemUsIAogICAgICAgICAgICAgIG91dHB1dF9wYXRoPW91dHB1dCwgCiAgICAgICAgICAgICAgbWluPW1pbl90ZW1wLCBtYXg9bWF4X3RlbXAsIAogICAgICAgICAgICAgIEhFPUhFLCAKICAgICAgICAgICAgICBmX2VjZGY9ZikKYGBgCgoKCgo=